﻿#region License

// Copyright (c) 2011, Matt Holmes
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted provided 
// that the following conditions are met:
// 
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the 
//   following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
//   the following disclaimer in the documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Serialization;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.ViewModel;
using Microsoft.Practices.ServiceLocation;

namespace SkyrimTweaker.Common {
	internal class ThemeInfo : NotificationObject {
		#region Properties

		public bool IsSelected {
			get { return Name == ThemeManager.CurrentTheme; }
			set {
				if (value) {
					ThemeManager.CurrentTheme = Name;
					RaisePropertyChanged (() => IsSelected);
				}
			}
		}

		public string Name { get; set; }

		#endregion

		#region Public Methods

		public void TriggerPropertyChange () {
			RaisePropertyChanged (() => IsSelected);
		}

		#endregion
	}

	[XmlRoot ("themeManifest")]
	public class ThemeManifest {
		#region Properties

		[XmlArray ("postloads")]
		public List<XamlFile> Postloads { get; set; }

		[XmlArray ("preloads")]
		public List<XamlFile> Preloads { get; set; }

		[XmlArray ("themes")]
		public List<ThemeElement> Themes { get; set; }

		#endregion
	}

	[XmlType ("theme")]
	public class ThemeElement {
		#region Properties

		[XmlArray ("files")]
		public List<XamlFile> Files { get; set; }

		[XmlAttribute ("name")]
		public string Name { get; set; }

		[XmlAttribute ("native")]
		public string Native { get; set; }

		#endregion
	}

	[XmlType ("xaml")]
	public class XamlFile {
		#region Properties

		[XmlAttribute ("file")]
		public string File { get; set; }

		#endregion
	}

	internal static class ThemeManager {
		#region Fields

		private static readonly XmlSerializer _Serializer = new XmlSerializer (typeof (ThemeManifest));
		private static readonly OrderedDictionary _ThemeManifests = new OrderedDictionary ();
		private static string _currentTheme;
		private static List<ThemeInfo> _themeInfo;

		#endregion

		#region Properties

		public static IEnumerable<ThemeInfo> ThemeInfo {
			get { return _themeInfo ?? (_themeInfo = ((ThemeManifest) _ThemeManifests.Cast<DictionaryEntry> ().First ().Value).Themes.Select (t => new ThemeInfo { Name = t.Name }).ToList ()); }
		}

		internal static string CurrentTheme {
			get { return _currentTheme; }
			set {
				_currentTheme = value;
				_themeInfo.ForEach (i => i.TriggerPropertyChange ());
				ServiceLocator.Current.GetInstance<IEventAggregator> ().GetEvent<SelectedThemChangedEvent> ().Publish (_currentTheme);
				ApplyTheme (_currentTheme);
			}
		}

		#endregion

		#region Public Methods

		public static void ApplyTheme (string name) {
			var preloads = new List<string> ();
			var postloads = new List<string> ();
			var themeFiles = new List<string> ();
			foreach (var entry in _ThemeManifests.Cast<DictionaryEntry> ().Select (e => new { Assembly = (string) e.Key, Manifest = (ThemeManifest) e.Value })) {
				preloads.AddRange (entry.Manifest.Preloads.Select (p => BuildUri (entry.Assembly, p.File)));
				postloads.AddRange (entry.Manifest.Postloads.Select (p => BuildUri (entry.Assembly, p.File)));

				var theme = entry.Manifest.Themes.FirstOrDefault (t => t.Name == name);
				if (theme != null) {
					if (!String.IsNullOrEmpty (theme.Native)) {
						themeFiles.Add (theme.Native);
					}

					themeFiles.AddRange (theme.Files.Select (f => BuildUri (entry.Assembly, f.File)));
				}
			}

			Application.Current.Resources.MergedDictionaries.Clear ();

			foreach (var part in preloads) {
				var skin = new ResourceDictionary { Source = new Uri (part) };
				Application.Current.Resources.MergedDictionaries.Add (skin);
			}

			_currentTheme = name;
			foreach (var part in themeFiles) {
				var skin = new ResourceDictionary { Source = new Uri (part) };
				Application.Current.Resources.MergedDictionaries.Add (skin);
			}

			foreach (var part in postloads) {
				var skin = new ResourceDictionary { Source = new Uri (part) };
				Application.Current.Resources.MergedDictionaries.Add (skin);
			}
		}

		public static void CacheThemeInfo (Assembly assm) {
			try {
				var uri = new Uri (String.Format ("pack://application:,,,/{0};component/Themes/ThemeManifest.xml", assm.FullName));
				var stream = Application.GetResourceStream (uri);
				var manifest = (ThemeManifest) _Serializer.Deserialize (stream.Stream);
				_ThemeManifests.Add (assm.FullName, manifest);
			}
			catch {
				return;
			}
		}

		#endregion

		#region Private Methods

		private static string BuildUri (string assm, string f) {
			return f.StartsWith ("pack://") ? f : String.Format ("pack://application:,,,/{0};component{1}", assm, f);
		}

		#endregion
	}
}
